package handlers

import (
	"bytes"
	"context"
	"deepseek-chat/internal/config"
	"deepseek-chat/internal/db"
	"deepseek-chat/internal/models"
	"deepseek-chat/internal/sysinfo"
	"deepseek-chat/internal/ws"
	"encoding/json"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"time"
)

type Handler struct {
	Hub *ws.Hub
}

func New(hub *ws.Hub) *Handler {
	return &Handler{Hub: hub}
}

func isDebug() bool { return os.Getenv("DEBUG") == "1" }
func dlog(format string, args ...interface{}) {
	if isDebug() {
		log.Printf("[DEBUG] "+format, args...)
	}
}

func snippet(b []byte, n int) string {
	if len(b) == 0 {
		return "<empty>"
	}
	if len(b) <= n {
		return string(b)
	}
	return string(b[:n]) + "..."
}

// Config API

func (h *Handler) HandleConfigKey(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		set := config.GlobalKeyStore.Get() != ""
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]bool{"set": set})
	case http.MethodPost:
		var body struct{ Key string `json:"key"` }
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Key == "" {
			http.Error(w, "invalid key", http.StatusBadRequest)
			return
		}
		
		if err := db.SaveEncryptedAPIKey(body.Key); err != nil {
			log.Printf("Failed to save encrypted API key: %v", err)
			http.Error(w, "failed to save key", http.StatusInternalServerError)
			return
		}
		
		config.GlobalKeyStore.Set(body.Key)
		
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]string{
			"status": "ok",
			"message": "API key encrypted and saved successfully",
		})
	default:
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
	}
}

// Topics API

func (h *Handler) HandleTopics(w http.ResponseWriter, r *http.Request) {
	dlog("Received request: %s %s", r.Method, r.URL.Path)
	switch r.Method {
	case http.MethodGet:
		rows, err := db.DB.Query("SELECT id, name, created_at FROM topics ORDER BY created_at ASC")
		if err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}
		defer rows.Close()
		var list []models.Topic
		for rows.Next() {
			var t models.Topic
			if err := rows.Scan(&t.ID, &t.Name, &t.CreatedAt); err != nil {
				http.Error(w, "db error", http.StatusInternalServerError)
				return
			}
			list = append(list, t)
		}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(list)
	case http.MethodPost:
		var body struct{ Name string `json:"name"` }
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Name == "" {
			http.Error(w, "invalid name", http.StatusBadRequest)
			return
		}
		id, err := db.RandomID()
		if err != nil {
			http.Error(w, "id error", http.StatusInternalServerError)
			return
		}
		now := time.Now().Unix()
		if _, err := db.DB.Exec("INSERT INTO topics(id, name, created_at) VALUES(?, ?, ?)", id, body.Name, now); err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}
		t := models.Topic{ID: id, Name: body.Name, CreatedAt: now}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(t)
	default:
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
	}
}

func (h *Handler) HandleDeleteTopic(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodDelete {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	
	path := strings.TrimPrefix(r.URL.Path, "/api/topics/")
	topicID := strings.TrimSuffix(path, "/")
	
	if topicID == "" {
		http.Error(w, "topic ID is required", http.StatusBadRequest)
		return
	}
	
	var exists bool
	err := db.DB.QueryRow("SELECT EXISTS(SELECT 1 FROM topics WHERE id = ?)", topicID).Scan(&exists)
	if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}
	
	if !exists {
		http.Error(w, "topic not found", http.StatusNotFound)
		return
	}
	
	_, err = db.DB.Exec("DELETE FROM topics WHERE id = ?", topicID)
	if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}
	
	w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]string{
			"status": "ok",
			"message": "topic deleted successfully",
		})
}

// Messages API

func (h *Handler) HandleGetMessages(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	
	topicID := r.URL.Query().Get("topicId")
	if topicID == "" {
		http.Error(w, "topicId is required", http.StatusBadRequest)
		return
	}
	
	rows, err := db.DB.Query(`
		SELECT id, topic_id, role, content, username, mine, timestamp, created_at 
		FROM messages 
		WHERE topic_id = ? 
		ORDER BY timestamp ASC
	`, topicID)
	if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}
	defer rows.Close()
	
	var messages []models.Message
	for rows.Next() {
		var m models.Message
		if err := rows.Scan(&m.ID, &m.TopicID, &m.Role, &m.Content, &m.Username, &m.Mine, &m.Timestamp, &m.CreatedAt); err != nil {
			http.Error(w, "db error", http.StatusInternalServerError)
			return
		}
		messages = append(messages, m)
	}
	
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(messages)
}

func (h *Handler) HandleCreateMessage(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	
	var body struct {
		TopicID   string `json:"topicId"`
		Role      string `json:"role"`
		Content   string `json:"content"`
		Username  string `json:"username"`
		Mine      bool   `json:"mine"`
		Timestamp int64  `json:"timestamp"`
	}
	
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		http.Error(w, "invalid json", http.StatusBadRequest)
		return
	}
	
	if body.TopicID == "" || body.Content == "" || body.Role == "" {
		http.Error(w, "topicId, role and content are required", http.StatusBadRequest)
		return
	}
	
	var exists bool
	err := db.DB.QueryRow("SELECT EXISTS(SELECT 1 FROM topics WHERE id = ?)", body.TopicID).Scan(&exists)
	if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}
	
	if !exists {
		http.Error(w, "topic not found", http.StatusNotFound)
		return
	}
	
	id, err := db.RandomID()
	if err != nil {
		http.Error(w, "id error", http.StatusInternalServerError)
		return
	}
	
	now := time.Now().Unix()
	if body.Timestamp == 0 {
		body.Timestamp = now * 1000
	}
	
	_, err = db.DB.Exec(`
		INSERT INTO messages(id, topic_id, role, content, username, mine, timestamp, created_at) 
		VALUES(?, ?, ?, ?, ?, ?, ?, ?)
	`, id, body.TopicID, body.Role, body.Content, body.Username, body.Mine, body.Timestamp, now)
	
	if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}
	
	message := models.Message{
		ID:        id,
		TopicID:   body.TopicID,
		Role:      body.Role,
		Content:   body.Content,
		Username:  body.Username,
		Mine:      body.Mine,
		Timestamp: body.Timestamp,
		CreatedAt: now,
	}
	
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(message)
}

func (h *Handler) HandleDeleteMessage(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodDelete {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	
	path := strings.TrimPrefix(r.URL.Path, "/api/messages/")
	messageID := strings.TrimSuffix(path, "/")
	
	if messageID == "" {
		http.Error(w, "message ID is required", http.StatusBadRequest)
		return
	}
	
	var exists bool
	err := db.DB.QueryRow("SELECT EXISTS(SELECT 1 FROM messages WHERE id = ?)", messageID).Scan(&exists)
	if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}
	
	if !exists {
		http.Error(w, "message not found", http.StatusNotFound)
		return
	}
	
	_, err = db.DB.Exec("DELETE FROM messages WHERE id = ?", messageID)
	if err != nil {
		http.Error(w, "db error", http.StatusInternalServerError)
		return
	}
	
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]string{
		"status": "ok",
		"message": "message deleted successfully",
	})
}

// System Status API

func (h *Handler) HandleStatus(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	
	status := sysinfo.GetSystemStatus(h.Hub.GetClientCount())
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(status)
}

func (h *Handler) HandleProcesses(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	
	processes := sysinfo.GetAllProcesses()
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]interface{}{
		"processes": processes,
		"count":     len(processes),
	})
}

// Chat API

func (h *Handler) HandleChat(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}

	type chatReq struct {
		Content string `json:"content"`
	}

	var req chatReq
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid json", http.StatusBadRequest)
		return
	}
	if req.Content == "" {
		http.Error(w, "content is required", http.StatusBadRequest)
		return
	}

	apiKey := config.GlobalKeyStore.Get()
	if apiKey == "" {
		http.Error(w, "missing api key", http.StatusUnauthorized)
		return
	}

	payload := map[string]interface{}{
		"model": "deepseek-chat",
		"messages": []map[string]string{
			{"role": "system", "content": "You are a helpful assistant."},
			{"role": "user", "content": req.Content},
		},
		"stream": false,
	}
	body, err := json.Marshal(payload)
	if err != nil {
		http.Error(w, "failed to build payload", http.StatusInternalServerError)
		return
	}
	dlog("/api/chat request content: %s", req.Content)
	dlog("/api/chat payload bytes=%d", len(body))

	ctx, cancel := context.WithTimeout(r.Context(), 120*time.Second)
	defer cancel()

	reqUp, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.deepseek.com/chat/completions", bytes.NewReader(body))
	if err != nil {
		http.Error(w, "request build error", http.StatusInternalServerError)
		return
	}
	reqUp.Header.Set("Content-Type", "application/json")
	reqUp.Header.Set("Accept", "application/json")
	reqUp.Header.Set("Authorization", "Bearer "+apiKey)
	reqUp.Header.Set("User-Agent", "deepseek-chat-demo/1.0")

	client := &http.Client{Timeout: 120 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }}
	dlog("/api/chat upstream POST %s", reqUp.URL.String())
	respUp, err := client.Do(reqUp)
	if err != nil {
		log.Printf("/api/chat upstream error: %v", err)
		http.Error(w, "upstream error", http.StatusBadGateway)
		return
	}
	defer respUp.Body.Close()
	out, readErr := io.ReadAll(respUp.Body)
	if readErr != nil {
		log.Printf("/api/chat upstream read error: %v", readErr)
		http.Error(w, "upstream read error", http.StatusBadGateway)
		return
	}
	dlog("/api/chat upstream status=%d len=%d", respUp.StatusCode, len(out))
	
	if respUp.StatusCode >= 300 && respUp.StatusCode < 400 {
		loc := respUp.Header.Get("Location")
		if loc == "" {
			log.Printf("/api/chat redirect without Location, status=%d", respUp.StatusCode)
			w.WriteHeader(respUp.StatusCode)
			w.Write(out)
			return
		}
		dlog("/api/chat redirect to %s", loc)
		reqUp2, err := http.NewRequestWithContext(ctx, http.MethodPost, loc, bytes.NewReader(body))
		if err != nil {
			http.Error(w, "redirect build error", http.StatusBadGateway)
			return
		}
		reqUp2.Header = reqUp.Header.Clone()
		dlog("/api/chat upstream POST (redirect) %s", reqUp2.URL.String())
		respUp2, err := client.Do(reqUp2)
		if err != nil {
			log.Printf("/api/chat redirect upstream error: %v", err)
			http.Error(w, "upstream error", http.StatusBadGateway)
			return
		}
		defer respUp2.Body.Close()
		out, readErr = io.ReadAll(respUp2.Body)
		if readErr != nil {
			log.Printf("/api/chat upstream (redirect) read error: %v", readErr)
			http.Error(w, "upstream read error", http.StatusBadGateway)
			return
		}
		dlog("/api/chat upstream (redirect) status=%d len=%d", respUp2.StatusCode, len(out))
		if respUp2.StatusCode >= 400 {
			log.Printf("/api/chat upstream status=%d body=%s", respUp2.StatusCode, string(out))
			w.WriteHeader(respUp2.StatusCode)
			w.Write(out)
			return
		}
	}
	if respUp.StatusCode >= 400 {
		log.Printf("/api/chat upstream status=%d body=%s", respUp.StatusCode, string(out))
		w.WriteHeader(respUp.StatusCode)
		w.Write(out)
		return
	}

	type dsResp struct {
		Choices []struct {
			Message struct {
				Role    string `json:"role"`
				Content string `json:"content"`
			} `json:"message"`
		} `json:"choices"`
	}
	var dr dsResp
	if err := json.Unmarshal(out, &dr); err != nil {
		log.Printf("/api/chat invalid response: %v, raw: %s", err, string(out))
		dlog("/api/chat parse error: %v, first 200 bytes: %s", err, snippet(out, 200))
		http.Error(w, "invalid API response", http.StatusBadGateway)
		return
	}

	assistant := ""
	if len(dr.Choices) > 0 {
		assistant = dr.Choices[0].Message.Content
	}

	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]string{"content": assistant})
}

func (h *Handler) HandleBalance(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}

	apiKey := config.GlobalKeyStore.Get()
	if apiKey == "" {
		http.Error(w, "missing api key", http.StatusUnauthorized)
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
	defer cancel()

	reqUp, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.deepseek.com/user/balance", nil)
	if err != nil {
		http.Error(w, "request build error", http.StatusInternalServerError)
		return
	}
	reqUp.Header.Set("Accept", "application/json")
	reqUp.Header.Set("Authorization", "Bearer "+apiKey)
	reqUp.Header.Set("User-Agent", "deepseek-chat-demo/1.0")
	client := &http.Client{Timeout: 30 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }}
	dlog("/api/balance upstream GET %s", reqUp.URL.String())
	respUp, err := client.Do(reqUp)
	if err != nil {
		log.Printf("/api/balance upstream error: %v", err)
		http.Error(w, "upstream error", http.StatusBadGateway)
		return
	}
	defer respUp.Body.Close()
	out, readErr := io.ReadAll(respUp.Body)
	if readErr != nil {
		log.Printf("/api/balance upstream read error: %v", readErr)
		http.Error(w, "upstream read error", http.StatusBadGateway)
		return
	}
	dlog("/api/balance upstream status=%d len=%d", respUp.StatusCode, len(out))
	if respUp.StatusCode >= 300 && respUp.StatusCode < 400 {
		loc := respUp.Header.Get("Location")
		if loc == "" {
			log.Printf("/api/balance redirect without Location, status=%d", respUp.StatusCode)
			w.WriteHeader(respUp.StatusCode)
			w.Write(out)
			return
		}
		dlog("/api/balance redirect to %s", loc)
		reqUp2, err := http.NewRequestWithContext(ctx, http.MethodGet, loc, nil)
		if err != nil {
			http.Error(w, "redirect build error", http.StatusBadGateway)
			return
		}
		reqUp2.Header = reqUp.Header.Clone()
		dlog("/api/balance upstream GET (redirect) %s", reqUp2.URL.String())
		respUp2, err := client.Do(reqUp2)
		if err != nil {
			log.Printf("/api/balance redirect upstream error: %v", err)
			http.Error(w, "upstream error", http.StatusBadGateway)
			return
		}
		defer respUp2.Body.Close()
		out, readErr = io.ReadAll(respUp2.Body)
		if readErr != nil {
			log.Printf("/api/balance upstream (redirect) read error: %v", readErr)
			http.Error(w, "upstream read error", http.StatusBadGateway)
			return
		}
		dlog("/api/balance upstream (redirect) status=%d len=%d", respUp2.StatusCode, len(out))
		if respUp2.StatusCode >= 400 {
			log.Printf("/api/balance upstream status=%d body=%s", respUp2.StatusCode, string(out))
			w.WriteHeader(respUp2.StatusCode)
			w.Write(out)
			return
		}
	}
	if respUp.StatusCode >= 400 {
		log.Printf("/api/balance upstream status=%d body=%s", respUp.StatusCode, string(out))
		w.WriteHeader(respUp.StatusCode)
		w.Write(out)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(out)
}
